﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Object = UnityEngine.Object;
using Common.Utils.Helper;
#if UNITY_EDITOR
#endif
using USClientKernel.Template;
using Game;


namespace Common.Utils
{
    public delegate void UpdateAssetBundleInfoLoadResult(float _progress, uint _loadedCount, bool _isDone, AssetBundle _ab);

    public class AssetBundleInfoLoadResult
    {
        private float m_progress;
        private bool m_isDone;
        private uint m_totalCount;
        private uint m_loadedCount;
        private AssetBundle m_assetBundle;

        public AssetBundleInfoLoadResult(uint _totalCount, out UpdateAssetBundleInfoLoadResult _ur)
        {
            m_progress = 0.0f;
            m_isDone = false;
            m_totalCount = _totalCount;
            m_loadedCount = 0;
            _ur = UpdateProperty;
        }

        private void UpdateProperty(float _progress, uint _loadedCount, bool _isDone, AssetBundle _ab)
        {
            m_progress = _progress;
            m_loadedCount = _loadedCount;
            m_isDone = _isDone;
            m_assetBundle = _ab;
        }

        public AssetBundle AssetBundle
        {
            // set { m_assetBundle = value; }
            get { return m_assetBundle; }
        }

        public float Progress
        {
            // set { m_progress = value; }
            get { return m_progress; }
        }

        public bool IsDone
        {
            // set { m_isDone = value; }
            get { return m_isDone; }
        }

        public uint TotalCount
        {
            get { return m_totalCount; }
        }

        public uint LoadedCount
        {
            // set { m_loadedCount = value; }
            get { return m_loadedCount; }
        }

    }

    public class AssetBundleInfo
    {
        private bool m_bIsLoading;
        private string m_name;
        private string[] m_arrDependencies;
        private AssetBundle m_assetBundle;
        public string Path { get; private set; }
        public int RefCount { get; set; }

        public AssetBundleInfo(string _path, string _name)
        {
            Path = _path;
            m_name = _name;
        }

        public AssetBundleInfo(string _path, string _name, string[] _dependencies)
        {
            Path = _path;
            m_name = _name;
            m_arrDependencies = _dependencies;
        }

        // 是否有效
        public bool IsValid
        {
            get { return null != m_assetBundle; } 
        }

        // 是否正在加载
        public bool IsLoading
        {
            get { return m_bIsLoading; }
        }

        // 是否加载完成
        public bool IsDone
        {
            get { return (null != m_assetBundle && !m_bIsLoading); }
        }

        // 获取名称
        public string Name
        {
            // set { m_assetBundle = value; }
            get { return m_name; }
        }

        // 同步加载
        public AssetBundle AssetBundle
        {
            get
            {
#if UNITY_EDITOR
                if (!IsValid)
                    _SearchAssetBundle();
#endif
                if (!IsValid && !IsLoading)
                    _AssetBundleLoad();
                if (null != m_assetBundle)
                    Debug.LogFormat("[{0}]: {1} sync load success", ReflectionHelper.GetCurrentClassName(), Path);
                else
					Debug.LogFormat("[{0}]: {1} sync load failed", ReflectionHelper.GetCurrentClassName(), Path);
				return m_assetBundle;
            }
        }


        /// <summary>
        /// AssetBundle协程加载
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        public IEnumerator LoadCoroutine(Action<AssetBundle> _loaded)
        {
            if (!IsValid && !IsLoading)
            {
                _AssetBundleLoad();
                Debug.LogFormat("[{0}]: {1} coroutine load success", ReflectionHelper.GetCurrentClassName(), Name);
                yield return null;
            }

            if (null != _loaded) _loaded(m_assetBundle);
            yield return null;
        }


        /// <summary>
        /// 在已经加载的池中寻找
        /// </summary>
        private AssetBundle _SearchAssetBundle()
        {
            AssetBundle ab = null;





            return ab;
        }


        /// <summary>
        /// AssetBundle加载
        /// </summary>
        private AssetBundle _AssetBundleLoad()
        {
            try
            {
                m_bIsLoading = true;
                _AssetBundleDependenciesLoad();
                m_assetBundle = AssetBundle.LoadFromFile(Path);
                m_bIsLoading = false;
            }
            catch (Exception e)
            {
                m_bIsLoading = false;
                Debug.Log(e.ToString());
                throw e;
            }
            return null;
        }


        /// <summary>
        /// AssetBundle的所有依赖项加载
        /// </summary>
        private void _AssetBundleDependenciesLoad()
        {
            if (null == m_arrDependencies) return;
            foreach (string dependency in m_arrDependencies)
            {
                AssetBundleInfo abi = AssetsBundleManager.Instance.GetAssetBundleInfo(dependency);
                if (null == abi.AssetBundle)
                    throw new Exception(string.Format("[{0}]: {1} async load dependency failed", ReflectionHelper.GetCurrentClassName(), Name));
            }
        }


        /// <summary>
        /// 异步加载AssetBundle
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        public IEnumerator LoadAsync(Action<AssetBundle> _loaded)
        {
            return LoadAsync(_loaded, null);
        }


        /// <summary>
        /// 异步加载AssetBundle
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        /// <param name="_progress">进度回调函数</param>
        public IEnumerator LoadAsync(Action<AssetBundle> _loaded, Action<AssetBundleCreateRequest> _progress)
        {
            //have load
            if (null != m_assetBundle)
            {
                _loaded(m_assetBundle);
                yield break;
            }

            //load AssetBundle
            m_bIsLoading = true;
            AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Path);

            while (!abcr.isDone)
            {
                if (abcr.progress >= 0.9f)
                    abcr.allowSceneActivation = true;
                if (null != _progress)
                    _progress(abcr);
                yield return null;
            }

            if (null != _progress)
                _progress(abcr);

            // 需要检查_Object 是否为null;
            m_assetBundle = abcr.assetBundle;
            m_bIsLoading = false;
            Debug.LogFormat("[{0}]: {1} async load success!", ReflectionHelper.GetCurrentClassName(), Name);

            if (null != _loaded)
                _loaded(m_assetBundle);

            yield return abcr;
        }


        /// <summary>
        /// 异步加载AssetBundle,会检查所有依赖项，没有加载的依赖项进行加载。
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        /// <param name="_progress">进度回调函数</param>
        public IEnumerator LoadAsyncEx(Action<AssetBundleInfoLoadResult> _rt = null, 
            Action<AssetBundle> _loaded = null, 
            Action<AssetBundleInfoLoadResult> _progress = null)
        {
            UpdateAssetBundleInfoLoadResult update;
            AssetBundleInfoLoadResult[] abilrArr = new AssetBundleInfoLoadResult[m_arrDependencies.Length];
            AssetBundleInfoLoadResult abilr = new AssetBundleInfoLoadResult((uint)m_arrDependencies.Length, out update);
            m_bIsLoading = true;

            if (null != _rt)
                _rt(abilr);
                

            float fTotalLoad = 1.0f;
            for (int i = 0; i < m_arrDependencies.Length; i++)
            {
                string dependency = m_arrDependencies[i];
                AssetBundleInfo abi = AssetsBundleManager.Instance.GetAssetBundleInfo(dependency);
                if (abi.IsValid)
                {
                    abilrArr[i] = null;
                }
                else
                {
                    CoroutineController.Instance.StartCoroutine(abi.LoadAsyncEx((AssetBundleInfoLoadResult _abilr) =>
                    {
                        abilrArr[i] = _abilr;
                    }));
                    yield return new WaitUntil(() => null != abilrArr[i]);
                    fTotalLoad += 1.0f;
                }
                yield return null;
            }

            uint uiLoadCnt = 0;
            while (uiLoadCnt < m_arrDependencies.Length)
            {
                uiLoadCnt = 0;
                float fCurrLoad = 0.0f;
                for (int i = 0; i < m_arrDependencies.Length; i++)
                {
                    AssetBundleInfoLoadResult _abilr = abilrArr[i];
                    if (null != _abilr)
                    {
                        if (_abilr.IsDone)
                            uiLoadCnt += 1;
                        fCurrLoad += _abilr.Progress;
                    }
                    else
                    {
                        uiLoadCnt += 1;
                    }
                }
                update(fCurrLoad / fTotalLoad, uiLoadCnt, false, null);
                if (null != _progress)
                    _progress(abilr);
                yield return null;
            }

            if (IsValid)
            {
                update(1.0f, 0, true, m_assetBundle);
                if (null != _progress)
                    _progress(abilr);
                if (null != _loaded)
                    _loaded(m_assetBundle);
                yield break;
            }

            AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Path);
            while (!abcr.isDone)
            {
                float fCurrLoad = fTotalLoad - 1.0f;
                if (abcr.progress >= 0.9f)
                    abcr.allowSceneActivation = true;
                if (abcr.isDone)
                    uiLoadCnt += 1;
                fCurrLoad += abcr.progress;

                Debug.LogFormat("[{0}]: {1} async load progress is {2}", ReflectionHelper.GetCurrentClassName(), Name, (fCurrLoad / fTotalLoad));
                update(fCurrLoad / fTotalLoad, uiLoadCnt, abcr.isDone, abcr.assetBundle);
                if (null != _progress)
                    _progress(abilr);
                yield return null;
            }

            if (abcr.isDone && null != abcr.assetBundle)
            {
                m_bIsLoading = false;
                m_assetBundle = abcr.assetBundle;
                uiLoadCnt = abilr.TotalCount;
                update(1.0f, uiLoadCnt, abcr.isDone, m_assetBundle);

                if (null != _progress)
                    _progress(abilr);
                if (null != _loaded)
                    _loaded(m_assetBundle);
                Debug.LogFormat("[{0}]: {1} async load success!", ReflectionHelper.GetCurrentClassName(), Name);
            }
            else
            {
                m_bIsLoading = false;
                throw new Exception(string.Format("[{0}]: {1} async load failed", ReflectionHelper.GetCurrentClassName(), Name));
            }

            yield return null;
        }
    }

    public class AssetsBundleManager : Singleton<AssetsBundleManager>//, IDisposable
    {
        //private bool m_bDisposed;
        private Dictionary<string, AssetBundleInfo> m_cacheAssetsBundle;
        private Dictionary<string, AssetBundleInfo> m_localAssetsBundle;
        private AssetBundle m_rootAssetBundle;
        private AssetBundleManifest m_rootManifest;

        protected override void Init()
        {
            Debug.LogFormat("[{0}]: AssetsBundleManager init!", ReflectionHelper.GetCurrentClassName());
            AssetBundle.UnloadAllAssetBundles(true);
            // m_bDisposed = false;
            m_cacheAssetsBundle = new Dictionary<string, AssetBundleInfo>();
            m_localAssetsBundle = new Dictionary<string, AssetBundleInfo>();
            m_rootAssetBundle = GetAssetBundleInfoByPath(GameDefine.ASSETS_BUNDLE_MANAGER_ROOT_NAME).AssetBundle;
            m_rootManifest = m_rootAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }

        //public void Dispose()
        //{
        //    Debug.Log("AssetsBundleManager Release");
        //    _Dispose();
        //    GC.SuppressFinalize(this);
        //}

        //protected virtual void _Dispose()
        //{
        //    if (m_bDisposed) return;
        //    AssetBundle.UnloadAllAssetBundles(true);
        //    m_bDisposed = true;
        //}

#region GetAssetBundleInfo

        private AssetBundleInfo GetAssetBundleInfoByPath(string _path)
        {
            return GetAssetBundleInfoByPath(_path, null);
        }

        private AssetBundleInfo GetAssetBundleInfoByPath(string _path, Action<AssetBundle> _loaded)
        {
            return GetAssetBundleInfo(_GenerateAssetBundleName(_path), _loaded);
        }

        public AssetBundleInfo GetAssetBundleInfo(string _assetBundleName)
        {
            return GetAssetBundleInfo(_assetBundleName, null);
        }

        public AssetBundleInfo GetAssetBundleInfo(string _assetBundleName, Action<AssetBundle> _loaded)
        {
            _assetBundleName = _assetBundleName.ToLower();
            AssetBundleInfo assetBundleInfo = null;
            if (m_cacheAssetsBundle.TryGetValue(_assetBundleName, out assetBundleInfo))
            {
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }

            string assetBundlePath = string.Concat(Application.persistentDataPath, "/", _assetBundleName);
            if (File.Exists(assetBundlePath))
            {
                assetBundleInfo = new AssetBundleInfo(assetBundlePath, _assetBundleName, GetAllDependencies(_assetBundleName));
                m_cacheAssetsBundle.Add(_assetBundleName, assetBundleInfo);
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }

            if (m_localAssetsBundle.TryGetValue(_assetBundleName, out assetBundleInfo))
            {
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }
#if UNITY_ANDROID
            assetBundlePath = string.Concat(Application.dataPath, "!assets/", _assetBundleName);
#else
            assetBundlePath = string.Concat(Application.streamingAssetsPath, "/", _assetBundleName);
#endif
            assetBundleInfo = new AssetBundleInfo(assetBundlePath, _assetBundleName, GetAllDependencies(_assetBundleName));
            m_localAssetsBundle.Add(_assetBundleName, assetBundleInfo);
            assetBundleInfo.RefCount++;
            return assetBundleInfo;
        }

        public string[] GetAllDependencies(string name)
        {
            if (!m_rootManifest)
                return null;
            return m_rootManifest.GetAllDependencies(name);
        }

        private string _GenerateAssetBundleName(string _path)
        {
            string rt = _path.Replace(@"\\", "/").Replace(@"\", "/");
            string rp = "Assets/";
            int idx = rt.IndexOf(rp);
            if (idx >= 0)
                rt = rt.Substring(idx + rp.Length);
            idx = rt.LastIndexOf("/");
            if (idx >= 0)
                rt = rt.Substring(0, idx);

            rt = rt.Replace(@"/", "_");

            if (!rt.StartsWith(GameDefine.ASSETS_BUNDLE_MANAGER_ROOT_NAME))
            {
                if (_path.EndsWith(".unity"))
                    rt = string.Concat(rt, ".u3d");
                else
                    rt = string.Concat(rt, ".pkg");

                // 移动端需要改名为小写
#if UNITY_IOS || UNITY_ANDROID
                rt = rt.ToLower();
#endif
            }
            return rt;
        }

#endregion


#region Load AssetBundle

        public AssetBundle Load(string _path)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path);
            if (null != _assetBundleInfo)
                return _assetBundleInfo.AssetBundle;
            return null;
        }

#endregion


#region Load Coroutine AssetBundle

        public void LoadCoroutine(string _path, Action<AssetBundle> _loaded)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            if (null != _assetBundleInfo)
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadCoroutine(_loaded));
        }

#endregion


#region Load Async AssetBundle

        public void LoadAsync(string _path, Action<AssetBundle> _loaded)
        {
            LoadAsync(_path, _loaded, null);
        }

        public void LoadAsync(string _path, Action<AssetBundle> _loaded, Action<AssetBundleCreateRequest> _progress)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            if (null != _assetBundleInfo)
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadAsync(_loaded, _progress));
        }

        public AssetBundleInfoLoadResult LoadAsyncEx(string _path, Action<AssetBundle> _loaded, Action<AssetBundleInfoLoadResult> _progress)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            AssetBundleInfoLoadResult _rt = null;
            if (null != _assetBundleInfo)
            {
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadAsyncEx((_abilr) =>
                {
                    _rt = _abilr;
                }
                , _loaded, _progress));
            }

            while(null == _rt) {}

            return _rt;
        }

#endregion


#region Get Asset Info In AssetBundleInfo Path

        private string _GetAssetNameFromPath(string _path)
        {
            string rt = _path.Replace(@"\\", "/").Replace(@"\", "/");

            int idx = rt.LastIndexOf("/");
            if (idx > 0)
                rt = rt.Substring(idx + 1, rt.Length - idx - 1);

            return rt;
        }

#endregion


#region Load Sync Asset

        public Object LoadAssetSync(string _path)
        {
            AssetBundle assetBundle = Load(_path);
            if (null == assetBundle)
                return null;
            return assetBundle.LoadAsset(_GetAssetNameFromPath(_path));
        }

#endregion


#region Load Coroutine AssetBundle

        public void LoadAssetCoroutine(string _path, Action<Object> _loaded)
        {
            LoadCoroutine(
                _path, 
                (AssetBundle __ab) => {
                    if (null != _loaded && null != __ab)
                        _loaded(__ab.LoadAsset(_GetAssetNameFromPath(_path)));
                    else if (null != _loaded)
                        _loaded(null);
                }
            );
        }

#endregion


#region Load Async Asset

        private IEnumerator _LoadAssetAsyncHelper(string _path, Action<Object> _loaded, Action<float> _progress, AssetBundleInfoLoadResult _abilr = null)
        {
            AssetBundle ab = _abilr.AssetBundle;
            if (null == ab && null == _loaded)
                yield break;
            if( null == ab)
                _loaded(null);

            // Load Asset Async
            string assetName = _GetAssetNameFromPath(_path);
            AssetBundleRequest abr = ab.LoadAssetAsync(assetName);
            while (!abr.isDone)
            {
                if (abr.progress >= 0.9f)
                    abr.allowSceneActivation = true;
                if (null != _progress)
                {
                    float fTotal = 1.0f;
                    float fCur = abr.progress;
                    if (null != _abilr)
                    {
                        fTotal += _abilr.TotalCount;
                        fCur += _abilr.TotalCount;
                    }
                    _progress(fCur/fTotal);
                }
                yield return null;
            }

            if (null != _progress)
                _progress(1.0f);

            // 需要检查_Object 是否为null;
            Object obj = abr.asset;
            Debug.LogFormat("[{0}]: {1} async load asset {2} success!", ReflectionHelper.GetCurrentClassName(), ab.name, assetName);

            if (null != _loaded)
                _loaded(obj);

            yield return null;
        }

        public AssetBundleInfoLoadResult LoadAssetAsync(string _path, Action<Object> _loaded, Action<float> _progress)
        {
            return LoadAsyncEx(
                _path,
                (AssetBundle __ab) => {},
                (AssetBundleInfoLoadResult __abilr) =>
                {
                    if (null != _progress)
                    {
                        float fTotal = __abilr.TotalCount + 1;
                        float fCur = __abilr.Progress * __abilr.TotalCount;
                        if (!__abilr.IsDone)
                            _progress(fCur / fTotal);
                        else
                            CoroutineController.Instance.StartCoroutine(_LoadAssetAsyncHelper(_path, _loaded, _progress, __abilr));
                    }
                }
            );
        }

#endregion
    }
}


